/**
 * OpenAtlasForAndroid Project
 * The MIT License (MIT) Copyright (OpenAtlasForAndroid) 2015 Bunny Blue,achellies
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author BunnyBlue
 **/
package com.openatlas.framework;

import android.app.Application;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.res.Resources;

import com.openatlas.hack.AndroidHack;
import com.openatlas.hack.AssertionArrayException;
import com.openatlas.hack.OpenAtlasHacks;
import com.openatlas.log.ILog;
import com.openatlas.log.Logger;
import com.openatlas.log.LoggerFactory;
import com.openatlas.log.OpenAtlasLog;
import com.openatlas.runtime.BundleLifecycleHandler;
import com.openatlas.runtime.ClassLoadFromBundle;
import com.openatlas.runtime.ClassNotFoundInterceptorCallback;
import com.openatlas.runtime.DelegateClassLoader;
import com.openatlas.runtime.DelegateComponent;
import com.openatlas.runtime.FrameworkLifecycleHandler;
import com.openatlas.runtime.InstrumentationHook;
import com.openatlas.runtime.PackageLite;
import com.openatlas.runtime.RuntimeVariables;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.FrameworkListener;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

public class Atlas {
    protected static Atlas instance;
    static final Logger log;
    private BundleLifecycleHandler bundleLifecycleHandler;
    private FrameworkLifecycleHandler frameworkLifecycleHandler;

    static {
        log = LoggerFactory.getInstance("Atlas");
    }


    public static Atlas getInstance() {
        if (instance != null) {
            return instance;
        }
        synchronized (Atlas.class) {
            if (instance == null) {
                instance = new Atlas();
            }

        }
        return instance;
    }

    public void init(Application application)
            throws Exception {
        String packageName = application.getPackageName();
        OpenAtlasHacks.defineAndVerify();
        ClassLoader classLoader = Atlas.class.getClassLoader();
        DelegateClassLoader delegateClassLoader = new DelegateClassLoader(classLoader);
        Framework.systemClassLoader = classLoader;
        RuntimeVariables.delegateClassLoader = delegateClassLoader;
        RuntimeVariables.delegateResources = initResources(application);
        RuntimeVariables.androidApplication = application;
        AndroidHack.injectClassLoader(packageName, delegateClassLoader);
        AndroidHack.injectInstrumentationHook(new InstrumentationHook(AndroidHack
                        .getInstrumentation(), application.getBaseContext()));
        injectApplication(application, packageName);
        this.bundleLifecycleHandler = new BundleLifecycleHandler();
        Framework.syncBundleListeners.add(this.bundleLifecycleHandler);
        this.frameworkLifecycleHandler = new FrameworkLifecycleHandler();
        Framework.frameworkListeners.add(this.frameworkLifecycleHandler);
        AndroidHack.hackH();
        // Framework.initialize(properties);
    }

    /**
     *@since 1.0.0
     * **/
    private Resources initResources(Application application) throws Exception {
        Resources resources = application.getResources();
        if (resources != null) {
            return resources;
        }
        log.error(" !!! Failed to get init resources.");
        return application.getPackageManager().getResourcesForApplication(application.getApplicationInfo());
    }

    public void injectApplication(Application application, String packageName)
            throws Exception {
        OpenAtlasHacks.defineAndVerify();
        AndroidHack.injectApplication(packageName, application);
    }

    public void startup(Properties properties) throws BundleException {
        Framework.startup(properties);
    }

//    public void startup() throws BundleException {
//        Framework.startup();
//    }

    public void shutdown() throws BundleException {
        Framework.shutdown(false);
    }

    public Bundle getBundle(String pkgName) {
        return Framework.getBundle(pkgName);
    }

    public Bundle getBundleOnDemand(String pkgName) {
        if (pkgName == null || pkgName.length() == 0) {
            return null;
        }
        if (Framework.getBundle(pkgName) == null) {
            ClassLoadFromBundle.checkInstallBundleAndDependency(pkgName);
        }
        return Framework.getBundle(pkgName);
    }

    public Bundle installBundle(String location, InputStream inputStream)
            throws BundleException {
        return Framework.installNewBundle(location, inputStream);
    }

    public Bundle installBundle(String location, File apkFile) throws BundleException {
        return Framework.installNewBundle(location, apkFile);
    }

    public void updateBundle(String pkgName, InputStream inputStream)
            throws BundleException {
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            bundle.update(inputStream);
            return;
        }
        throw new BundleException("Could not update bundle " + pkgName
                + ", because could not find it");
    }

    public void updateBundle(String pkgName, File mBundleFile) throws BundleException {
        if (!mBundleFile.exists()) {
            throw new BundleException("file not  found" + mBundleFile.getAbsolutePath());
        }
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            bundle.update(mBundleFile);
            return;
        }
        throw new BundleException("Could not update bundle " + pkgName
                + ", because could not find it");
    }

    public boolean restoreBundle(String[] packageNames) {

        return Framework.restoreBundle(packageNames);
    }

    public void installOrUpdate(String[] packageNames, File[] bundleFiles)
            throws BundleException {
        Framework.installOrUpdate(packageNames, bundleFiles);
    }

    public void uninstallBundle(String pkgName) throws BundleException {
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            BundleImpl bundleImpl = (BundleImpl) bundle;
            try {
                File archiveFile = bundleImpl.getArchive().getArchiveFile();
                if (archiveFile.canWrite()) {
                    archiveFile.delete();
                }
                bundleImpl.getArchive().purge();
                File revisionDir = bundleImpl.getArchive().getCurrentRevision()
                        .getRevisionDir();
                bundle.uninstall();
                if (revisionDir != null) {
                    Framework.deleteDirectory(revisionDir);
                    return;
                }
                return;
            } catch (Exception e) {
                log.error("uninstall bundle error: " + pkgName + e.getMessage());
                return;
            }
        }
        throw new BundleException("Could not uninstall bundle " + pkgName + ", because could not find it");
    }

    public List<Bundle> getBundles() {
        return Framework.getBundles();
    }

    public Resources getDelegateResources() {
        return RuntimeVariables.delegateResources;
    }

    public ClassLoader getDelegateClassLoader() {
        return RuntimeVariables.delegateClassLoader;
    }

    public Class<?> getComponentClass(String pkgName) throws ClassNotFoundException {
        return RuntimeVariables.delegateClassLoader.loadClass(pkgName);
    }

    public ClassLoader getBundleClassLoader(String pkgName) {
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            return ((BundleImpl) bundle).getClassLoader();
        }
        return null;
    }

    public PackageLite getBundlePackageLite(String pkgName) {
        return DelegateComponent.getPackage(pkgName);
    }

    public File getBundleFile(String pkgName) {
        Bundle bundle = Framework.getBundle(pkgName);
        if (bundle != null) {
            return ((BundleImpl) bundle).archive.getArchiveFile();
        }
        return null;
    }

    public InputStream openAssetInputStream(String packageName, String assetName)
            throws IOException {
        Bundle bundle = Framework.getBundle(packageName);
        if (bundle != null) {
            return ((BundleImpl) bundle).archive.openAssetInputStream(assetName);
        }
        return null;
    }

    public InputStream openNonAssetInputStream(String packageName, String assetName)
            throws IOException {
        Bundle bundle = Framework.getBundle(packageName);
        if (bundle != null) {
            return ((BundleImpl) bundle).archive.openNonAssetInputStream(assetName);
        }
        return null;
    }

    public void addFrameworkListener(FrameworkListener frameworkListener) {
        Framework.addFrameworkListener(frameworkListener);
    }

    public void removeFrameworkListener(FrameworkListener frameworkListener) {
        Framework.removeFrameworkListener(frameworkListener);
    }

    public void addBundleListener(BundleListener bundleListener) {
        Framework.addBundleListener(bundleListener);
    }

    public void removeBundleListener(BundleListener bundleListener) {
        Framework.removeBundleListener(bundleListener);
    }

    public void onLowMemory() {
        this.bundleLifecycleHandler.handleLowMemory();
    }

    public void enableComponent(String componentName) {
        PackageLite packageLite = DelegateComponent.getPackage(componentName);
        if (packageLite != null && packageLite.disableComponents != null) {
            for (String disableComponent : packageLite.disableComponents) {
                PackageManager packageManager = RuntimeVariables.androidApplication
                        .getPackageManager();
                ComponentName componentName2 = new ComponentName(
                        RuntimeVariables.androidApplication.getPackageName(),
                        disableComponent);
                try {
                    packageManager.setComponentEnabledSetting(componentName2, 1,
                            1);
                    log.debug("enableComponent: "
                            + componentName2.getClassName());
                } catch (Exception e) {
                    log.error("enableComponent error: "
                            + componentName2.getClassName() + e.getMessage());
                }
            }
        }
    }

    public void setLogger(ILog iLog) {
        OpenAtlasLog.setExternalLogger(iLog);
    }


    public void setClassNotFoundInterceptorCallback(
            ClassNotFoundInterceptorCallback classNotFoundInterceptorCallback) {
        Framework.setClassNotFoundCallback(classNotFoundInterceptorCallback);
    }

}
